﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AddIn.EA.Architecture.Validation.BusinessEntity;
//using AddIn.EA.Architecture.CatalogService.BusinessLogic;
using AddIn.EA.Framework;

namespace AddIn.EA.Architecture.Validation.BusinessLogic
{
    public class ServiceValidator : IValidator
    {
        private RepositoryManager repository { get; set; }
        private Package service { get; set; }
        private List<string> wasValidatedList { get; set; }

        public ServiceValidator(RepositoryManager repository, Package service)
        {
            this.repository = repository;
            this.service = service;
            this.wasValidatedList = new List<string>();
        }

        #region IValidator Members

        /// <summary>
        /// Valida o preenchimento do EA
        /// </summary>
        /// <returns>Lista de erros</returns>
        public ValidateErrorList Validate()
        {
            var errors = new ValidateErrorList();

            errors.AddRange(ValidateInterfaces());
            errors.AddRange(ValidateMethods());
            errors.AddRange(ValidateEnums());
            errors.AddRange(ValidateDataContracts("Request"));
            errors.AddRange(ValidateDataContracts("Response"));

            return errors;
        }

        #endregion

        #region Interfaces

        /// <summary>
        /// Retorna os elementos das interfaces
        /// </summary>
        /// <returns></returns>
        private List<Element> GetInterfaceElements(ValidateErrorList errors)
        {
            List<Element> interfaceElements = null;
            var serviceContract = this.service.GetPackageByName("ServiceContract");

            if (serviceContract != null)
            {
                interfaceElements =
                    (from el in serviceContract.Elements
                     where el.Type == ElementType.Interface &&
                         el.Stereotype == "ServiceContract"
                     select el).ToList();
            }

            return interfaceElements;
        }
        
        /// <summary>
        /// Valida o preenchimento do EA
        /// </summary>
        private ValidateErrorList ValidateInterfaces()
        {
            var errors = new ValidateErrorList();
            var interfaceElements = GetInterfaceElements(errors);

            if (interfaceElements != null)
            {
                if (interfaceElements.Count > 0)
                {
                    // Percorrer as interfaces do servico
                    foreach (var interfaceElement in interfaceElements)
                    {
                        if (string.IsNullOrEmpty(interfaceElement.Notes.Trim()))
                            errors.AddNotesError(interfaceElement.GetFullName());
                    }
                }
                else
                    errors.AddError("ServiceContract", "Nenhuma interface encontrada");
            }
            else
                errors.AddError(this.service.Name, "Package ServiceContract não encontrada");

            return errors;
        }
        
        #endregion

        #region Methods

        /// <summary>
        /// Valida o preenchimento do EA
        /// </summary>
        private ValidateErrorList ValidateMethods()
        {
            var errors = new ValidateErrorList();
            var interfaceElements = GetInterfaceElements(errors);

            if (interfaceElements != null && interfaceElements.Count > 0)
            {
                // Percorrer todas interfaces do servico
                foreach (var interfaceElement in interfaceElements)
                {
                    var methodElements = interfaceElement.Methods
                        .Where(x => string.IsNullOrEmpty(x.Stereotype)).ToList();

                    if (methodElements != null && methodElements.Count > 0)
                    {
                        // Percorrer os metodos da interface
                        foreach (var methodElement in methodElements)
                        {
                            if (string.IsNullOrEmpty(methodElement.GetNotes().Trim()))
                                errors.AddNotesError(methodElement.GetFullName());

                            // Percorrer os parametros do metodo
                            if (methodElement.Parameters != null && methodElement.Parameters.Count > 0)
                            {
                                foreach (var paramElement in methodElement.Parameters)
                                {
                                    if (string.IsNullOrEmpty(paramElement.Notes.Trim()))
                                        errors.AddError(methodElement.GetFullName(), string.Format(
                                            "Notes não está preenchido no parâmetro \"{0}\" do método",
                                            paramElement.Name));
                                }
                            }

                            // Validar retorno
                            if (!string.IsNullOrEmpty(methodElement.ReturnType.Trim()) &&
                                methodElement.ReturnType.Trim() != "void")
                            {
                                if (string.IsNullOrEmpty(methodElement.GetReturns().Trim()))
                                    errors.AddError(methodElement.GetFullName(), "Notes não está preenchido no retorno do método");
                            }

                            // Validar pre conditions
                            if (methodElement.PreConditions != null && methodElement.PreConditions.Count > 0)
                            {
                                // Validar transacional
                                var transacional = (from pc in methodElement.PreConditions
                                                    where pc.Type == "Transacional"
                                                    select pc).ToList();

                                if (transacional == null || transacional.Count == 0)
                                    errors.AddError(methodElement.GetFullName(), "Transacional não cadastrado na aba PreCondition do método");
                                else if (transacional.Count > 1)
                                    errors.AddError(methodElement.GetFullName(), "Mais de um item Transacional cadastrado na aba PreCondition do método");
                                else if (transacional[0].Name.Trim() != "Sim" && transacional[0].Name.Trim() != "Não")
                                    errors.AddError(methodElement.GetFullName(), "Transacional deve ser cadastrado como \"Sim\" ou \"Não\" na aba PreCondition do método");

                                // Validar exceções
                                var excecoes = (from pc in methodElement.PreConditions
                                                where pc.Type == "ValidationFault" ||
                                                      pc.Type == "BusinessFault"
                                                select pc).ToList();

                                if (excecoes != null && excecoes.Count > 0)
                                {
                                    foreach (var excecao in excecoes)
                                    {
                                        long id;
                                        if (string.IsNullOrEmpty(excecao.Name.Trim()))
                                            errors.AddError(methodElement.GetFullName(), "ID da exceção não cadastrado na aba PreCondition do método");
                                        else if (!long.TryParse(excecao.Name.Trim(), out id))
                                            errors.AddError(methodElement.GetFullName(), "ID da exceção deve ser numérico na aba PreCondition do método");

                                        if (string.IsNullOrEmpty(excecao.Type.Trim()))
                                            errors.AddError(methodElement.GetFullName(), "Tipo da exceção não cadastrado na aba PreCondition do método");

                                        if (string.IsNullOrEmpty(excecao.Notes.Trim()))
                                            errors.AddError(methodElement.GetFullName(), "Condição da exceção não cadastrado na aba PreCondition do método");
                                    }
                                }
                            }
                            else
                                errors.AddError(methodElement.GetFullName(), "Informações não cadastradas na aba PreCondition do método");
                        }
                    }
                    else
                        errors.AddError(interfaceElement.GetFullName(), "Nenhum método encontrado na interface");
                }
            }

            return errors;
        }

        #endregion

        #region Enums

        /// <summary>
        /// Retorna os elementos dos enums
        /// </summary>
        /// <returns></returns>
        private List<Element> GetEnumElements()
        {
            var dataContract = this.service.GetPackageByName("DataContract");
            if (dataContract == null || dataContract.GetPackageByName("Enum") == null)
                return null;

            var enumElements =
                (from el in dataContract.GetPackageByName("Enum").Elements
                 where el.Type == ElementType.Class &&
                     el.Stereotype == "enumeration"
                 select el).ToList();

            return enumElements;
        }

        /// <summary>
        /// Valida o preenchimento dos enums no EA
        /// </summary>
        private ValidateErrorList ValidateEnums()
        {
            var errors = new ValidateErrorList();
            var enumElements = GetEnumElements();

            if (enumElements != null && enumElements.Count > 0)
            {
                foreach (var enumElement in enumElements)
                {
                    if (string.IsNullOrEmpty(enumElement.Notes.Trim()))
                        errors.AddNotesError(enumElement.GetFullName());

                    if (enumElement.Attributes != null && enumElement.Attributes.Count > 0)
                    {
                        foreach (var enumItemElement in enumElement.Attributes)
                        {
                            if (string.IsNullOrEmpty(enumItemElement.Notes.Trim()))
                                errors.AddNotesError(enumItemElement.GetFullName());
                        }
                    }
                    else
                        errors.AddError(enumElement.GetFullName(), "Nenhum item encontrado no enum");
                }
            }

            return errors;
        }

        #endregion

        #region DataContracts

        /// <summary>
        /// Se existir retorna o pai do elemento
        /// </summary>
        /// <param name="dataContractElement"></param>
        /// <returns></returns>
        private Element GetParentDataContractElement(Element dataContractElement)
        {
            var dataContract = this.service.GetPackageByName("DataContract");
            Element parentDataContractElement = null;

            // Verificar se existe heranca
            if (dataContractElement.Connectors != null && dataContractElement.Connectors.Count > 0)
            {
                var generalizationConnector = dataContractElement.Connectors.Where(x => x.Type == Connector.ConnectorType.Generalization).SingleOrDefault();

                if (generalizationConnector != null)
                {
                    // Se sim buscar elemento pai nas classes base
                    parentDataContractElement = dataContract.GetElementById(generalizationConnector.SupplierID);
                }
            }

            return parentDataContractElement;
        }

        /// <summary>
        /// Retorna os elementos dos datacontracts
        /// </summary>
        /// <param name="package"></param>
        /// <returns></returns>
        private List<Element> GetDataContractElements(string package)
        {
            var dataContract = this.service.GetPackageByName("DataContract");
            if (dataContract == null || dataContract.GetPackageByName(package) == null)
                return null;

            var dataContractElements =
                (from el in dataContract.GetPackageByName(package).Elements
                 where el.Type == ElementType.Class &&
                     el.Stereotype == "DataContract"
                 select el).ToList();

            return dataContractElements;
        }

        /// <summary>
        /// Valida o preenchimento dos datacontracts no EA
        /// </summary>
        /// <param name="package"></param>
        private ValidateErrorList ValidateDataContracts(string package)
        {
            var errors = new ValidateErrorList();
            var dataContractElements = GetDataContractElements(package);

            if (dataContractElements != null && dataContractElements.Count > 0)
            {
                foreach (var dataContractElement in dataContractElements)
                {
                    bool childHasProperties;
                    errors.AddRange(ValidateMember(dataContractElement, out childHasProperties, false));

                    // Buscar elemento pai nas classes base
                    var parentDataContractElement = GetParentDataContractElement(dataContractElement);

                    if (parentDataContractElement != null)
                    {
                        if (!wasValidatedList.Exists(x => x == parentDataContractElement.GetFullName()))
                        {
                            bool parentHasProperties;
                            errors.AddRange(ValidateMember(parentDataContractElement, out parentHasProperties, true));
                            wasValidatedList.Add(parentDataContractElement.GetFullName());

                            // Se o pai nao tem propriedades disparar erro
                            if (!parentHasProperties)
                                errors.AddError(parentDataContractElement.GetFullName(), "Nenhuma propriedade encontrada na classe");
                        }
                    }
                    else
                    {
                        // Se o filho não tem pai e nao tem propriedades disparar erro
                        if (!childHasProperties)
                            errors.AddError(dataContractElement.GetFullName(), "Nenhuma propriedade encontrada na classe");
                    }

                }
            }

            return errors;
        }

        /// <summary>
        /// Valida um datacontract
        /// </summary>
        /// <param name="dataContractElement">Elemento</param>
        /// <param name="hasProperties">True se tiver propriedades ou false se não tiver</param>
        /// <param name="isParent">É um item pai ou nao</param>
        /// <returns>Lista de erros</returns>
        private ValidateErrorList ValidateMember(Element dataContractElement, out bool hasProperties, bool isParent)
        {
            var errors = new ValidateErrorList();

            if (string.IsNullOrEmpty(dataContractElement.Notes.Trim()) && !isParent)
                errors.AddNotesError(dataContractElement.GetFullName());

            // Buscar propriedades do elemento
            var propertyElements = (from el in dataContractElement.Methods
                                    where el.Scope == ElementScope.Public &&
                                       el.Stereotype == "property"
                                    select el).ToList();

            // Validar propriedades
            if (propertyElements != null && propertyElements.Count > 0)
            {
                foreach (var propertyElement in propertyElements)
                {
                    if (string.IsNullOrEmpty(propertyElement.Notes.Trim()))
                        errors.AddNotesError(propertyElement.GetFullName());
                }
                hasProperties = true;
            }
            else
                hasProperties = false;

            return errors;
        }

        #endregion

    }
}
